﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System8Equations;
using System.Windows.Threading;
using System.Linq;

namespace System8ToyMC.Plots
{
    /// <summary>
    /// The central manager for all the plots we are managing and everything else.
    /// </summary>
    public class PlotManager
    {
        public PlotManager()
        {
            Loops = 1000;
        }

        /// <summary>
        /// Simple interface to capture the run-as-a-plot semantics so 1D, 2D, etc. can look
        /// the same to the processing engine.
        /// </summary>
        private interface IRunAPlot
        {
            Action Setup(S8EqnSeriesAccumulator eqn);

            Histogram GetPlot();

            bool Match(string varPaht);
        }

        /// <summary>
        /// Get/Set the number of iterations we should fill each plot with
        /// </summary>
        public int Loops { get; set; }

        /// <summary>
        /// Keep track of all the plots we are going to want to be showing.
        /// </summary>
        private List<IRunAPlot> _plots = new List<IRunAPlot>();

        class Plot1D : IRunAPlot
        {
            private Histogram _hist;
            private string _path;

            /// <summary>
            /// Create plotter obj for 1D plot
            /// </summary>
            /// <param name="hist"></param>
            /// <param name="pathToPlot"></param>
            public Plot1D(Histogram hist, string pathToPlot)
            {
                this._hist = hist;
                this._path = pathToPlot;
            }

            /// <summary>
            /// Set ourselves up for a run.
            /// </summary>
            /// <param name="eqn"></param>
            /// <returns></returns>
            public Action Setup(S8EqnSeriesAccumulator eqn)
            {
                var waiter = eqn.Watch(_path);
                return () => ExtractResults(waiter, eqn);
            }

            /// <summary>
            /// Called when the eqns have been run. Needs to be called on the
            /// UI thread or bad things happen (true in Silverlight, right?).
            /// We are not on the UI thread (potentially) so we need to dispatch this...
            /// </summary>
            /// <param name="lookupReference"></param>
            public void ExtractResults(EqnSeriesAccumulator.ColumnReference lookupReference, S8EqnSeriesAccumulator eqn)
            {
                _hist.HistogramValues = eqn.GetValueList(lookupReference);
            }

            /// <summary>
            /// Return the plot - a hack for now...
            /// </summary>
            /// <returns></returns>
            public Histogram GetPlot()
            {
                return _hist;
            }

            /// <summary>
            /// Returns true if the path is a match!
            /// </summary>
            /// <param name="path"></param>
            /// <returns></returns>
            public bool Match(string path)
            {
                return _path == path;
            }
        }

        /// <summary>
        /// Add a plot to the list of plots we are taking care of. The data to plot comes
        /// from a single variable (i.e. it is a 1D histogram).
        /// </summary>
        /// <param name="hist"></param>
        /// <param name="pathToPlot"></param>
        /// <returns></returns>
        public void AddPlot1D(Histogram hist, string pathToPlot)
        {
            ///
            /// Configure the title correctly
            /// 

            string t = pathToPlot.Replace('/', '.');
            if (t.EndsWith("Value"))
            {
                t = t.Substring(0, t.Length - 5);
            }
            hist.XAxisName = t;
            _plots.Add(new Plot1D(hist, pathToPlot));
        }

        /// <summary>
        /// Remove the plot from our cataloging!
        /// </summary>
        /// <param name="varPaht"></param>
        public void RemovePlot(string varPaht)
        {
            var pInfo = from p in _plots
                        where p.Match(varPaht)
                        select p;

            var plt = pInfo.FirstOrDefault();
            if (plt == null)
                return;

            _plots.Remove(plt);
        }

        /// <summary>
        /// Find a plot in the list. Return null if we don't have it.
        /// </summary>
        /// <param name="varPath"></param>
        /// <returns></returns>
        public Histogram FindPlot(string varPath)
        {
            var pInfo = from p in _plots
                        where p.Match(varPath)
                        select p;

            var plt = pInfo.FirstOrDefault();
            if (plt == null)
                return null;

            return plt.GetPlot();
        }

        /// <summary>
        /// Clear out and re-calc all the plots!
        /// </summary>
        /// <param name="system8Equations"></param>
        public void Calculate(System8Equations.S8Equations templateEqns)
        {
            ///
            /// Now, setup the eqns
            /// 

            S8EqnSeriesAccumulator eqn = new S8EqnSeriesAccumulator();
            eqn.S8 = templateEqns;

            if (_batchUpdateInProgress)
            {
                _savedCalculation = eqn;
            }
            else
            {
                QueueFinishCalculation(eqn);
            }
        }

        /// <summary>
        /// Submits the finish calc to a background queue.
        /// </summary>
        /// <param name="eqn"></param>
        private void QueueFinishCalculation(S8EqnSeriesAccumulator eqn)
        {
            System.Threading.ThreadPool.QueueUserWorkItem(obj => FinishCalculation(eqn));
        }

        /// <summary>
        /// Finishes up the actual calculation...
        /// </summary>
        /// <param name="eqn"></param>
        private void FinishCalculation(S8EqnSeriesAccumulator eqn)
        {
            List<Action> finishUp = new List<Action>();
            _plots.ForEach(p => finishUp.Add(p.Setup(eqn)));

            _vary.ForEach(v => eqn.SetOnIteration(v));

            ///
            /// Do the loops
            /// 

            eqn.Loop(Loops);

            ///
            /// Now finalize the results.
            /// 
            
            finishUp.ForEach(a => { if (a != null) a(); });
        }

        /// <summary>
        /// True if batch updates are going on and we should just hold back.
        /// </summary>
        private bool _batchUpdateInProgress = false;

        /// <summary>
        /// Stores up a calculation to be run later.
        /// </summary>
        private S8EqnSeriesAccumulator _savedCalculation = null;

        /// <summary>
        /// We are starting a batch of numbers updates - so don't replot - just hold until everything has been updated.
        /// </summary>
        public void BeginBatchUpdate()
        {
            _batchUpdateInProgress = true;
        }

        /// <summary>
        /// The batch updates are done - run the calculation with the last set of numbers we got
        /// </summary>
        public void EndBatchUpdate()
        {
            _batchUpdateInProgress = false;
            if (_savedCalculation != null)
            {
                QueueFinishCalculation(_savedCalculation);
                _savedCalculation = null;
            }
        }

        /// <summary>
        /// Remove a variable if it is there.
        /// </summary>
        /// <param name="p"></param>
        public void RemoveVaryingVariable(string path)
        {
            var v = FindVariable(path);
            if (v == null)
                return;

            _vary.Remove(v);
        }

        /// <summary>
        /// Remove all variables we are varying
        /// </summary>
        public void RemoveAllVaryingVariables()
        {
            _vary.Clear();
        }

        /// <summary>
        /// Keeps track of the variables that we are going to vary when we do the
        /// calculation.
        /// </summary>
        private List<S8Variable> _vary = new List<S8Variable>();

        /// <summary>
        /// Use a variable that will vary as we do the calculation
        /// </summary>
        /// <param name="s8"></param>
        public void AddVaryingVariable(S8Variable s8)
        {
            _vary.Add(s8);
        }

        /// <summary>
        /// See if we have avariable in there.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public bool HasVariable(string path)
        {
            var goodVar = FindVariable(path);
            return goodVar != null;
        }

        /// <summary>
        /// Finds a particular variable or returns null.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private S8Variable FindVariable(string path)
        {
            var matches = from v in _vary
                          where v.Path == path
                          select v;

            var goodVar = matches.FirstOrDefault();
            return goodVar;
        }
    }
}
